package com.flute.tools.data.berkeleydb;

import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentLockedException;
import com.sleepycat.je.EnvironmentNotFoundException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.VersionMismatchException;

/**
 * Implementation of IStorage for Berkeley DB
 * 
 */
public class BdbStorage implements IStorage {
	private static final Logger LOGGER = Logger.getLogger(BdbStorage.class
			.getName());

	// 数据库环境
	private BdbEnvironment env = null;

	// 数据库
	private Database policys = null;

	// 数据绑定
	private EntryBinding<Object> dataBinding = null;

	// object lock
	private ReentrantLock lock = null;

	// Bdb name
	private String name;

	/**
	 * Constructor of BdbStorage
	 * 
	 * @param name
	 * @param env
	 */
	public BdbStorage(String name, BdbEnvironment env) {
		this.name = name;
		this.env = env;
		this.lock = new ReentrantLock();
		this.init();
	}

	private void init() {
		try {
			DatabaseConfig dbConfig = new DatabaseConfig();
			dbConfig.setAllowCreate(true);
			dbConfig.setSortedDuplicates(false);
			dbConfig.setDeferredWrite(true);
			policys = env.openDatabase(null, this.name, dbConfig);
			dataBinding = new SerialBinding<Object>(env.getClassCatalog(),
					Object.class);
			LOGGER.info("bdb init success");
		} catch (EnvironmentNotFoundException e) {
			e.printStackTrace();
		} catch (EnvironmentLockedException e) {
			e.printStackTrace();
		} catch (VersionMismatchException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
	}

	/**
	 * close BDB and release resources
	 * 
	 * @throws DatabaseException
	 */
	public void close() throws DatabaseException {
		if (policys != null) {
			policys.close();
		}
	}

	@Override
	public void put(Object key, Object value) {
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			lock.lock();
			cursor = policys.openCursor(null, null);
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry valueEntry = new DatabaseEntry();
			dataBinding.objectToEntry(key, keyEntry);
			dataBinding.objectToEntry(value, valueEntry);
			status = cursor.put(keyEntry, valueEntry);
			if (status != OperationStatus.SUCCESS) {
				LOGGER.info("save date error");
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
			lock.unlock();
		}
	}

	@Override
	public Object search(Object key) {
		if (key == null) {
			throw new RuntimeException("The key can't be null");
		}
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			cursor = policys.openCursor(null, null);
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry valueEntry = new DatabaseEntry();
			dataBinding.objectToEntry(key, keyEntry);
			status = cursor.getSearchKey(keyEntry, valueEntry, null);
			if (status != OperationStatus.SUCCESS
					|| valueEntry.getData() == null
					|| valueEntry.getData().length <= 0) {
				return null;
			}
			return dataBinding.entryToObject(valueEntry);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	@Override
	public Object getFirst() {
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			DatabaseEntry key = new DatabaseEntry();
			DatabaseEntry value = new DatabaseEntry();
			cursor = policys.openCursor(null, null);
			status = cursor.getFirst(key, value, null);
			if (status != OperationStatus.SUCCESS || key.getData() == null
					|| key.getData().length < 1 || value.getData() == null
					|| value.getData().length < 1) {
				return null;
			}
			return dataBinding.entryToObject(value);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	@Override
	public void delete(Object key) {
		OperationStatus status = null;
		try {
			lock.lock();
			DatabaseEntry keyEntry = new DatabaseEntry();
			dataBinding.objectToEntry(key, keyEntry);
			status = policys.delete(null, keyEntry);
			if (status == OperationStatus.SUCCESS) {
				LOGGER.log(Level.INFO, "delete data success! key=" + name);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			lock.unlock();
		}
	}

	@Override
	public Object next() {
		return null;
	}

	@Override
	public Object next(Object primaryKey) {
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			lock.lock();
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry valueEntry = new DatabaseEntry();
			cursor = policys.openCursor(null, null);
			if (primaryKey != null) {
				dataBinding.objectToEntry(primaryKey, keyEntry);
				status = cursor.getSearchKey(keyEntry, valueEntry, null);
			}
			status = cursor.getNext(keyEntry, valueEntry, null);
			if (status != OperationStatus.SUCCESS) {
				return null;
			}
			return dataBinding.entryToObject(valueEntry);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				try {
					cursor.close();
				} catch (DatabaseException e) {
				}
			}
			lock.unlock();
		}
	}

	@Override
	public boolean isContains(Object key) {
		if (this.search(key) != null) {
			return true;
		}
		return false;
	}

	/**
	 * 取得数据名称
	 */
	public String getName() {
		return this.name;
	}

	@Override
	public void sync() {
		policys.sync();
	}
}
